%%% ====================================================================
%%% A second (and 3rd..) pass translating XSD to model used by 
%%% erlsom_parse.
%%% 
%%% Copyright (C) 2006 Willem de Jong
%%%
%%% This program is free software; you can redistribute it and/or modify
%%% it under the terms of the GNU General Public License as published by 
%%% the Free Software Foundation; either version 2 of the License, or 
%%% (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful, but
%%% WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License 
%%% along with this program; if not, write to the Free Software 
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%%% USA
%%%
%%% Author contact: w.a.de.jong@gmail.com
%%% ====================================================================

%%% Version: 0.0.3

-module(erlsom_pass2).
-export([secondPass/2]).
-export([pass5/2]).
-import(erlsom_lib, [findType/2]). %% the record definitions
-include("erlsom_compile.hrl").
-include("erlsom.hrl").
-include("erlsom_parse.hrl").
-define(MAX_PASSES, 7).

%% translate 'min' and 'max'
%% translate types
%% add sequence numbers and totals
%% add namespace element

%% typeInfo - the intermediate format.
%% global (true or false): we need to find out in the
%% end whether this type should be available as 'top level' element in the 
%% xml document.
%% -record(typeInfo, 
%%         {typeName, global, typeType, typeRef, elements, attributes}).
%% -record(elementInfo, {alternatives, min, max}).
%% -record(alternative, {tag, type, real, min, max}).
%% -record(attribute, {name, optional, type}).
%% -record(schemaInfo, {targetNamespace, elementFormDefault, namespacePrefix, namespaces, path=[]}).


%% debug(Text) -> io:format("pass2: ~p\n", [Text]).
%% debug(Text1, Text2) -> io:format("pass2: ~p - ~p\n", [Text1, Text2]).

%% debug_types([]) -> true;
%% debug_types([#type{nm = Name}| Tail]) -> 
  %% debug(Name),
  %% debug_types(Tail).

%% debug(Text1, Text2) ->
  %% io:format("~p ~p\n", [Text1, Text2]).

secondPass(IntermediateStruct, Info = #schemaInfo{namespaces=NS, targetNamespace = Tns}) ->
  {Types1, GlobalElements} = translateTypes(IntermediateStruct, 
                            [], [], IntermediateStruct, Info),
  %% Note: pass 4 is done before pass 3 (seems to be better).
  Types2 = pass4(Types1, Info),
  Types3 = pass3(Types2),
  Types5 = [make_Document(GlobalElements, [], Info) | Types3],
  Types6 = pass5(Types5, Info),
  #model{tps = Types6, nss = NS, tns = Tns}.


translateTypes([Type | Tail], Acc, GlobalAcc, Types, Info) ->
  {TranslatedType, TypeOfType} = translateType(Type, Types, Info),
  case TypeOfType of
    globalElement ->
      translateTypes(Tail, [TranslatedType| Acc], [Type|GlobalAcc], Types, Info);
    globalElementRefOnly ->
      translateTypes(Tail, Acc, [Type|GlobalAcc], Types, Info);
    noType ->
      translateTypes(Tail, Acc, GlobalAcc, Types, Info);
    _Else ->
      translateTypes(Tail, [TranslatedType| Acc], GlobalAcc, Types, Info)
  end;
translateTypes([], Acc, GlobalAcc, _Types, _Info) ->
  {Acc, GlobalAcc}.

%% {'_document', sequence, [{[{'xs:schema', 'schemaType', true, 1, 1}], 1, 1, 1}], [], 1}
make_Document([#typeInfo{typeName=Name, typeRef=Type}|Tail], Acc, Info)
  when Type /= undefined ->
  make_Document(Tail, [#alt{tag = list_to_atom(Name), 
                            tp = list_to_type(Type), rl = true, mn = 1, mx = 1}|Acc], Info);
make_Document([#typeInfo{typeName=Name}|Tail], Acc, Info) ->
  make_Document(Tail, [#alt{tag = list_to_atom(Name), 
                            tp = list_to_atom(Name), rl = true, mn = 1, mx = 1}|Acc], Info);
make_Document([], Acc, _Info) ->
  #type{nm = '_document', tp = sequence, 
        els = [#el{alts = Acc, mn = 1,  mx = 1, nr = 1}], 
        atts = [], 
        nr = 1}.
  

%% Each Type is of the form {TypeName, TypeType, Elements, Attributes, NrOfElements}.
%% - TypeName is an atom.
%% - TypeType is currently always 'sequence', meaning that the elements have to
%%   be received in the specified order. In the future it might be considered to
%%   add something like 'bag' or 'set'.
%% - Elements is a list of Element. These Elements correspond to the elements
%%   that together form the Type.
%% - Attributes is a list of Attribute. Each Attribute describes an attribute
%%   that may occur as part of the Type.
%% - NrOfElements is an integer that indicates the number of elements in the Type.

%% resolve 'extended' types: look up the base and add its elemens and attributes
translateType(Type = #typeInfo{elements=Elemts, attributes=Attrs, extends = Base, anyAttr = AnyAttr}, Types, 
                               Info = #schemaInfo{namespaces=NS})
  when Base /= undefined ->
  case lists:keysearch(erlsom_lib:makeRef(Base, NS), #typeInfo.typeName, Types) of
    {value, #typeInfo{elements = BaseEls, attributes = BaseAttrs, anyAttr = BaseAnyAttr, extends = Base2}} ->
      %% debug(Elemts),
      %% debug(BaseEls),
      %% debug(Attrs),
      %% debug(BaseAttrs),
      NewAnyAttr = if BaseAnyAttr == undefined -> AnyAttr; true -> BaseAnyAttr end,
      translateType(Type#typeInfo{elements = BaseEls ++ Elemts, %% TODO: will never be 'undefined'?
                                  attributes = BaseAttrs ++ Attrs,  
				  anyAttr = NewAnyAttr,
                                  extends = Base2}, Types, Info);
    _Else ->
      throw({error, "Base type not found: " ++ erlsom_lib:makeRef(Base, NS)})
  end;

%% this corresponds with simple types. They don't have to be included in the model,
%% since references will be replaced by {#PCDATA, ...} type.
translateType(#typeInfo{typeType = simpleType}, _Types, _Info) ->
  {[], noType};

translateType(#typeInfo{typeName=Name, typeRef=undefined,
                        typeType = TypeType,
                        elements=Elemts, attributes=Attrs,
			anyAttr = AnyAttrValue,
                        mixed = Mixed,
                        seqOrAll = SorA, min = Min, max = Max}, Types, Info) ->
  TypeName=list_to_atom(Name),
  %% 'anyAttribute' can be defined in the type, but also in an attribute group
  {Attributes, AnyAttr} = translateAttributes(Attrs, {[], undefined}, 1, Info, 1),
  AnyAttr2 = if 
               AnyAttr == undefined -> AnyAttrValue; 
               true -> AnyAttr
             end,
  NrOfElements = erlsom_lib:listLength(Elemts) + 
                 erlsom_lib:listLength(Attributes) +1,
  Elements = translateElements(Elemts, [], erlsom_lib:listLength(Attributes) +1, 
                               Types),
  case Mixed of
    true ->
      Elements2 = makeMixed(Elements);
    _NotMixed ->
      Elements2 = Elements
  end,
  {#type{nm = TypeName, tp = seqOrAll(SorA), els = Elements2, atts = Attributes, nr = NrOfElements,
         mn = Min, mx = Max, anyAttr = AnyAttr2}, TypeType};

translateType(#typeInfo{typeName=Name, typeRef=Type,
                        typeType = TypeType,
                        elements=undefined, %% attributes=undefined,
                        seqOrAll = SorA,
			anyAttr = AnyAttrValue,
                        min = Min,
                        max = Max}, _Types, _Info) ->
  TypeName=list_to_atom(Name),
  {#type{nm = TypeName, tp = seqOrAll(SorA), 
         els = [#el{alts = [#alt{tag = TypeName, 
                                 tp = list_to_type(Type), 
                                 rl = true, 
                                 mn = 1, 
                                 mx = 1}], 
                    mn = 1, mx = 1, nr = 1}], 
         atts = [], nr = 2, mn = Min, mx = Max, anyAttr = AnyAttrValue}, TypeType}.

seqOrAll(all) -> all;
seqOrAll(_) -> sequence.

%% purpose of 'Count' is to make sure that we don't get into an infinite loop in case 
%% of circular refernces in attributeGroups
translateAttributes(_, _Acc, _SeqNr, _Info, 10) ->
  throw({error, "circular reference in attribute group?"});
translateAttributes(undefined, _Acc, _SeqNr, _Info, _Count) ->
  {[], undefined};
translateAttributes([Attribute | Tail], {Acc, AnyAttr}, SeqNr, Info, Count) ->
  %% 'Attribute' can also be a reference to an attribute group - which may include 'anyAttribute'
  {TranslatedAttributes, AnyAttr2} = translateAttribute(Attribute, SeqNr, Info, Count),
  AnyAttr3 = if AnyAttr2 == undefined -> AnyAttr; true -> AnyAttr2 end,
  translateAttributes(Tail, {TranslatedAttributes ++ Acc, AnyAttr3}, SeqNr + length(TranslatedAttributes), Info, Count);
translateAttributes([], {Acc, AnyAttr}, _SeqNr, _Info, _Count) ->
  {lists:reverse(Acc), AnyAttr}.

translateElements(undefined, _Acc, _SeqNr, _Types) ->
  [];
translateElements([Element | Tail], Acc, SeqNr, Types) ->
  %% debug(Element),
  translateElements(Tail, [translateElement(Element, SeqNr, Types) | Acc], SeqNr+1, Types);
translateElements([], Acc, _SeqNr, _Types) ->
  lists:reverse(Acc).

%% -record(elementInfo, {alternatives, min, max}).
%%
%% Each Element is of the form {Alternatives, MinOccurs, MaxOccurs,
%% SequenceNumber}.
%% 
%% - Alternatives is a list of Aternative. Quite often there will be only 1
%%   alternative. If there are several possibilities that may occur at this
%%   position in the type (a 'choice', in XSD terminology), they will be listed
%%   here.
%% - MinOccurs is the number of times this Element may occur. The value 0 means
%%   that the Element is optional.
%% - MaxOccurs is the maximum number of times the Element may occur. The value
%%   'unbound' indicates that there is no limit.
%% - SequenceNumber is the position of the Element in the Elements list. This
%%   value is redundant, it is only there because I thought it would be easier,
%%   and maybe more performant.

translateElement(#elementInfo{alternatives=Alternatives, min=Min, max=Max}, SeqNr, Types) ->
  #el{alts = translateAlternatives(Alternatives, [], Types), 
      mn = Min, 
      mx = Max, nr = SeqNr}.

translateAlternatives([Alternative | Tail], Acc, Types) ->
  translateAlternatives(Tail, [translateAlternative(Alternative, Types) | Acc], Types);
translateAlternatives([], Acc, _Types) ->
  lists:reverse(Acc).

%% -record(alternative, {tag, type, real, min, max}).
%% Each Alternative is of the form {Tag, TypeReference, RealElement, min, max}.
%% - Tag is generally the tag of the element. In case of a 'choice', the
%%   combination of the Tag and the next event determines which altenative is
%%   selected. Tag can also be '#text', in which case we expect a 'character'
%%   event.
%% - TypeReference is either a reference to a Type (a TypeName), or a tuple
%%   {#PCDATA, PredefinedType}. This corresponds roughly to the next state of the
%%   state machine. In otherwords: "If we receive a start tag A, look for the
%%   Alternative with Tag=A, and start parsing a type as indicated by
%%   TypeReference".
%% - RealElement can be 'true' or 'false'. If it is 'false', then the current
%%   event will be left in the input stream. If it is 'true', the current event
%%   will be removed from the stream.
translateAlternative(#alternative{tag=Tag, type=Type, real=Real, min=Min, max=Max, anyInfo = AnyInfo}, Types) ->
  %% if Type refers to an element in Types that is actually just a simple
  %% type (string, for example), then we don't put a reference, but the type
  %% itself.
  %%
  %% If Type refers to an element in Types that is a globelElement that is 
  %% just a reference to another type, then we put that second reference.
  case lists:keysearch(Type, #typeInfo.typeName, Types) of
    %% -record(typeInfo, {typeName, global, typeType, typeRef, elements, attributes}).
    {value, #typeInfo{typeRef="##string"}} ->
      %% debug("Tag " ++ Tag),
      #alt{tag = list_to_atom(Tag), tp = list_to_type("##string"), rl = Real, mn = Min, mx = Max, anyInfo = AnyInfo};
    {value, #typeInfo{typeType = globalElementRefOnly, typeRef=Ref, elements=undefined, attributes=[]}} ->
      %% debug("Tag: " ++ Tag ++ " Ref: " ++ Ref),
      #alt{tag = list_to_atom(Tag), tp = list_to_type(Ref), rl = Real, mn = Min, mx = Max, anyInfo = AnyInfo};
    %% If Type has only 1 element and this element has only 1 alternative with tag = '#text' and 'real'  = false
    %% *and* the element has no attributes, then there is no point in referring to that type.
    %% However, this remains on the TODO list for now.
    _Else ->
      #alt{tag = list_to_atom(Tag), tp = list_to_type(Type), rl = Real, mn = Min, mx = Max, anyInfo = AnyInfo}
  end.
  

%% pass3 replaces, for choices that have several alternatives, the elements with a 
%% sort of group (real = false, new type is introduced) (see example10)
pass3(Types) ->
  pass3(Types, []).

pass3([Type|Tail], Acc) ->
  pass3(Tail, pass3Type(Type) ++ Acc);
pass3([], Acc) ->
  lists:reverse(Acc).

pass3Type(#type{nm = TypeName, els = Elements} = Rec) ->
  {ThisTypesElements, NewTypes} = pass3Elements(Elements, TypeName),
  [Rec#type{els = ThisTypesElements} | NewTypes].

pass3Elements(Elements, TypeName) ->
  pass3Elements(Elements, TypeName, [], []).

pass3Elements([Element|Tail], TypeName, Acc, NewTypes) ->
  {TranslatedElement, Types} = pass3Element(Element, TypeName),
  pass3Elements(Tail, TypeName, [TranslatedElement | Acc], Types ++ NewTypes);
pass3Elements([], _TypeName, Acc, NewTypes) ->
  {lists:reverse(Acc), NewTypes}.

pass3Element(#el{alts = Alternatives, mn = Min} = Rec, TypeName)->
  %% if one of the alternatives is optional, the whole choice is optional!
  {ThisElementsAlternatives, NewTypes, Optional} = pass3Alternatives(Alternatives, TypeName),
  if 
    Optional -> Min2 = 0;
    true -> Min2 = Min
  end,
  if 
    length(Alternatives) /= 1 ->
      {Rec#el{alts = ThisElementsAlternatives, mn = Min2}, NewTypes};
    true ->
      {Rec#el{mn = Min2}, []}
  end.

pass3Alternatives(Alternatives, TypeName) ->
  pass3Alternatives(Alternatives, TypeName, [], [], false).

pass3Alternatives([Alternative | Tail], TypeName, Acc, NewTypes, Optional) ->
  {TranslatedAlternative, Types} = pass3Alternative(Alternative, TypeName),
  Min = TranslatedAlternative#alt.mn,
  Optional2 = if 
                 Min == 0 -> true;
                 true -> Optional
              end,
  pass3Alternatives(Tail, TypeName, [TranslatedAlternative | Acc], Types ++ NewTypes, Optional2);
pass3Alternatives([], _TypeName, Acc, NewTypes, Optional) ->
  {Acc, NewTypes, Optional}.

pass3Alternative(Alternative = #alt{tag = Name, rl = Real}, TypeName) ->
   if 
     Real == false ->
       {Alternative, []};
     true ->
       NewTypeName = 
          if 
            %% TODO: this probably doesn't make any sense. I guess 'any' types
            %% should be handled as above (return {Alternative, []}).
            Name == '#any' -> list_to_atom(atom_to_list(TypeName) ++ "-any");
            true -> list_to_atom(atom_to_list(TypeName) ++ "-" ++ erlsom_lib:nameWithoutPrefix(atom_to_list(Name)))
          end,
       %% debug(NewTypeName),
       {Alternative#alt{tp = NewTypeName, rl = false}, 
         [#type{nm = NewTypeName, tp = sequence, 
                els = [#el{alts = [Alternative#alt{mn = 1, mx = 1}], 
                           mn = 1, mx = 1, nr = 1}], 
                atts = [], 
                nr = 2}]}
   end.


%% pass4 replaces the tags of group references (##TODO) with the first tag of the 
%% group definition.
%% It also takes into account the fact that, if all elements of the group are optional,
%% the whole group is optional (minOccurs = 0)
pass4(Types, Info) ->
  pass4(Types, ?MAX_PASSES, Info).

pass4(_Types, 0, _Info) ->
  throw({error, "Group or anonymous sequence nested too deep"});
pass4(Types, Count, Info) ->
  put(erlsom_pass4_ready, true),
  Result = pass4(Types, [], Types, Count, Info),
  case get(erlsom_pass4_ready) of 
    true -> 
      erase(erlsom_pass4_ready),
      Result;
    _Else ->
      pass4(Result, Count - 1, Info)
  end.

pass4([Type|Tail], Acc, Types, Count, Info) ->
  pass4(Tail, [pass4Type(Type, Types, Count, Info) | Acc], Types, Count, Info);
pass4([], Acc, _Types, _Count, _Info) ->
  lists:reverse(Acc).

pass4Type(Rec = #type{els = Elements}, Types, Count, Info) ->
  ThisTypesElements = pass4Elements(Elements, Types, Count, Info),
  Rec#type{els = ThisTypesElements}.

pass4Elements(Elements, Types, Count, Info) ->
  pass4Elements(Elements, [], Types, Count, Info).

pass4Elements([Element|Tail], Acc, Types, Count, Info) ->
  TranslatedElement = pass4Element(Element, Types, Count, Tail, Info),
  pass4Elements(Tail, [TranslatedElement | Acc], Types, Count, Info);
pass4Elements([], Acc, _Types, _Count, _Info) ->
  lists:reverse(Acc).

pass4Element(Element = #el{alts = Alternatives, mn = Min}, Types, Count, NextElements, Info)->
  {ThisElementsAlternatives, Optional} = pass4Alternatives(Alternatives, Types, Count, NextElements, Info),
  Min2 = if Optional == true -> 0; true -> Min end,
  Element#el{alts = ThisElementsAlternatives, mn = Min2}.

pass4Alternatives(Alternatives, Types, Count, NextElements, Info) ->
  pass4Alternatives(Alternatives, [], true, Types, Count, NextElements, Info).

%% returns {TranslatedAlternatives, Optional}
pass4Alternatives([Alternative | Tail], Acc, Optional, Types, Count, NextElements, Info) ->
  {TranslatedAlternatives, Optional2} = pass4Alternative(Alternative, Types, Count, NextElements, Info),
  Optional3 = if Optional2 == false -> false; true -> Optional end,
  pass4Alternatives(Tail, TranslatedAlternatives ++ Acc, Optional3, Types, Count, NextElements, Info);
pass4Alternatives([], Acc, Optional, _Types, _Count, _NextElements, _Info) ->
  {Acc, Optional}.

%% returns {TranslatedAlternatives, Optional}
pass4Alternative(Alternative = #alt{tag = Name, tp = Type}, Types, _Count, _NextElements, _Info) ->
  case Name of
    '##TODO' ->
      %% Search the type, and look for the first tag.
      %% debug_types(Types),
      case lists:keysearch(Type, #type.nm, Types) of
        {value, Record} ->
           getTagFromType(Alternative, Record#type.els);
        _Else ->
          throw({error, "Group definition not found " ++ atom_to_list(Type)})
      end;
    _Else ->
      {[Alternative], false}
  end.

%% end of pass 4

%% Pass5 also deals with 'any' elements.
%% - add the 'nxt' values that signal the end of the 'any' bit
%% - add (concrete) alternatives to all 'any' alternatives.
%% added should be all alternatives from the '_document' type that meet the criteria wrt namespace
%% The criteria have to be kept as part of the 'any' alternative (in the final model), since we have
%% to be able to repeat this when a namespace is added to the model.
%%
%% it has to be done after the '_document' type has been created!
pass5(Types, Info) ->
  pass5(Types, [], Types, Info).

pass5([Type|Tail], Acc, Types, Info) ->
  pass5(Tail, [pass5Type(Type, Types, Info) | Acc], Types, Info);
pass5([], Acc, _Types, _Info) ->
  lists:reverse(Acc).

pass5Type(Rec = #type{els = Elements}, Types, Info) ->
  ThisTypesElements = pass5Elements(Elements, Types, Info),
  Rec#type{els = ThisTypesElements}.

pass5Elements(Elements, Types, Info) ->
  pass5Elements(Elements, [], Types, Info).

pass5Elements([Element|Tail], Acc, Types, Info) ->
  TranslatedElement = pass5Element(Element, Types, Tail, Info),
  pass5Elements(Tail, [TranslatedElement | Acc], Types, Info);
pass5Elements([], Acc, _Types, _Info) ->
  lists:reverse(Acc).

pass5Element(Element = #el{alts = Alternatives}, Types, NextElements, Info)->
  ThisElementsAlternatives = pass5Alternatives(Alternatives, Types, NextElements, Info),
  Element#el{alts = ThisElementsAlternatives}.

pass5Alternatives(Alternatives, Types, NextElements, Info) ->
  pass5Alternatives(Alternatives, [], Types, NextElements, Info).

pass5Alternatives([Alternative | Tail], Acc, Types, NextElements, Info) ->
  TranslatedAlternatives = pass5Alternative(Alternative, Types, NextElements, Info),
  pass5Alternatives(Tail, TranslatedAlternatives ++ Acc, Types, NextElements, Info);
pass5Alternatives([], Acc, _Types, _NextElements, _Info) ->
  Acc.

pass5Alternative(Alternative = #alt{tag = Name, anyInfo = AnyInfo}, Types, NextElements, Info) ->
  case Name of
    '#any' ->  
      #anyInfo{prCont = Pc}  = AnyInfo, 
      case Pc of
        "lax" ->
          [Alternative#alt{nxt=getNextTags(NextElements)} | getDocumentAlternatives(AnyInfo, Types, Info)];
        _ ->
          [Alternative | getDocumentAlternatives(AnyInfo, Types, Info)]
      end;
    _Else ->
      [Alternative]
  end.

%% returns {TranslatedAlternatives, Optional}
getTagFromType(Alternative, [#el{mn = 0, alts = Alternatives} | Tail]) ->
  %% if Tail == [] -> debug("all alternatives are optional!"); true -> true end,
  {Alternatives2, Optional} = getTagFromType(Alternative, Tail),
  {getTagFromAlternatives(Alternative, Alternatives) ++ Alternatives2, Optional};
getTagFromType(Alternative, [#el{alts = Alternatives} | _Tail]) ->
  {getTagFromAlternatives(Alternative, Alternatives), false};
getTagFromType(_Alternative, []) ->
  {[], true}.

getTagFromAlternatives(Alternative, [#alt{tag = '##TODO'} | Tail])->
  put(erlsom_pass4_ready, false),
  %% debug("need to do another pass"),
  [Alternative#alt{tag = '##TODO'} | getTagFromAlternatives(Alternative, Tail)];
getTagFromAlternatives(Alternative, [#alt{tag = Name} | Tail])->
  [Alternative#alt{tag = Name} | getTagFromAlternatives(Alternative, Tail)];
getTagFromAlternatives(_Alternative, [])->
  [].

%% Add the 'nxt' values that signal the end of the 'any' bit
%% Get the alternatives of the next element. IF the next element is optional, also get the alternatives 
%% of the element after that, etc.
getNextTags(NextElements) ->
  getNextTags(NextElements, []).

getNextTags([], Acc) ->
  Acc;
getNextTags([#el{alts = Alternatives, mn = 0} | Tail], Acc) ->
  getNextTags(Tail, getTags(Alternatives) ++ Acc);
getNextTags([#el{alts = Alternatives} | _Tail], Acc) ->
  getTags(Alternatives) ++ Acc.

getTags(Alternatives) ->
  getTags(Alternatives, []).

getTags([], Acc) ->
  Acc;
getTags([#alt{tag=Tag} | Tail], Acc) ->
  getTags(Tail, [Tag | Acc]).
  
  
%% get the alternatives from the '_document' type
%% -record(anyInfo, {prCont, ns}). %% for any elements
getDocumentAlternatives(AnyInfo, Types, Info) ->   
  #type{els = [#el{alts = Alternatives}]} = findType('_document', Types), %% only 1 element
  getMatchingAlts(Alternatives, AnyInfo, Info).

getMatchingAlts(Alternatives, #anyInfo{ns = Namespaces}, Info) ->
  case Namespaces of 
    "##any" ->
      Alternatives;
    _  ->
      getMatchingAlts(Alternatives, [], Namespaces, Info)
  end.

getMatchingAlts([], Acc, _Namespaces, _Info) ->
  Acc;
getMatchingAlts([Alt = #alt{tag=Tag} | Tail], Acc, Namespaces, Info = #schemaInfo{namespaces=NS, targetNamespace = Tns}) ->
  Namespace = erlsom_lib:tagNamespace(Tag, NS),
  case Namespaces of
    "##local" -> 
      case Namespace of
        undefined -> 
          getMatchingAlts(Tail, [Alt | Acc], Namespaces, Info);
        _Else ->
          getMatchingAlts(Tail, Acc, Namespaces, Info)
      end;
    "##other" -> 
      case Namespace of
        undefined -> 
          getMatchingAlts(Tail, Acc, Namespaces, Info);
        Tns -> 
          getMatchingAlts(Tail, Acc, Namespaces, Info);
        _Else ->
          getMatchingAlts(Tail, [Alt | Acc], Namespaces, Info)
      end
  end.

          

list_to_type("##string") ->
  {'#PCDATA', 'char'};

list_to_type(Type = {'#PCDATA', _}) ->
  Type;

list_to_type(undefined) ->
  {'#PCDATA', 'char'};

list_to_type(List) ->
  list_to_atom(List).

%% -record(attribute, {name, optional, type}).
%% Attribute is a tuple {Name, SequenceNr, Optional, Type}
translateAttribute(#attribute{name=Name, optional=Optional, type = Type, ref = undefined}, 
                   SeqNo, _Info, _Count) ->
  {[#att{nm = list_to_atom(Name), nr = SeqNo, opt = trueFalse(Optional), tp = attributeType(Type)}], undefined};

translateAttribute(#attribute{ref = Ref, optional = Optional}, SeqNo, Info= #schemaInfo{namespaces=NS}, _Count) ->
  Name = erlsom_lib:makeRef(Ref, NS),
  case lists:keysearch(Name, #attribute.name, Info#schemaInfo.atts) of
    {value, #attribute{name = Name, type=Type, optional=Optional2}} ->
      Optional3 = if Optional == undefined -> Optional2; true -> Optional end,
      {[#att{nm = list_to_atom(Name), nr = SeqNo, opt = trueFalse(Optional3), tp = attributeType(Type)}], undefined};
    _ ->
      if 
        Name == "xml:lang" ->
          {[#att{nm = list_to_atom(Name), nr = SeqNo, opt = trueFalse(Optional), tp = 'char'}], undefined};
        true ->
          throw({error, "Attribute not found: " ++ erlsom_lib:makeRef(Ref, NS)})
      end
  end;

%% -record(attributeGroupRefType, {elInfo, ref}).
translateAttribute(#attributeGroupRefType{ref=Ref}, SeqNo, Info = #schemaInfo{namespaces=NS}, Count) ->
  %% look for atributeGroup
  %% -record(attGrp, {name, atts, anyAttr}).
  case lists:keysearch(erlsom_lib:makeRef(Ref, NS), #attGrp.name, Info#schemaInfo.attGrps) of
    {value, #attGrp{atts = Attrs, anyAttr = AnyAttrValue}} ->
      %% translate recursively
      {Attributes, AnyAttr} = translateAttributes(Attrs, {[], undefined}, SeqNo, Info, Count + 1),
      AnyAttr2 = if 
                   AnyAttrValue == undefined -> AnyAttr; 
                   true -> AnyAttrValue
                 end,
      {Attributes, AnyAttr2};
    _Else -> 
      throw({error, "Attribute group not found: " ++ erlsom_lib:makeRef(Ref, NS)})
  end.

attributeType(undefined) -> 'char';
attributeType(#qname{uri = NS, localPart = Local}) -> 
  case NS of
    "http://www.w3.org/2001/XMLSchema" ->
      erlsom_lib:translateType(Local);
    _Else ->
      'char'
  end.

%% add a #text alternative to every element.
%% note that this only makes sense for elements with maxOccurs > 1, but that is the 
%% most likely way to use 'mixed' anyway.
makeMixed(Elements) ->
  makeMixed(Elements, []).

makeMixed([#el{mx = 1} | _Tail], _Acc) ->
  throw({error, "Mixed only supported for elements with maxOccurs > 1"});
makeMixed([Element = #el{} | Tail], Acc) ->
  makeMixed(Tail, [makeElementMixed(Element) | Acc]);
makeMixed([], Acc) ->
  Acc.

makeElementMixed(El = #el{alts = Alts}) ->
  El#el{alts = [ #alt{tag = '#text', tp = {'#PCDATA', 'char'}, rl=false} | Alts]}.

trueFalse(undefined) -> true; %% atributes are optional by default
trueFalse("required") -> false;
trueFalse("prohibited") -> true;  %% TODO: this is obviously not correct
trueFalse("optional") -> true.

